#include "dkglTerrain.h"
//#include "dkglLog.h"
#include "dkglUtility.h"
#include "dkglVector3.h"
#include <limits>

namespace DKGL
{

using namespace std;

TerrainUnit::TerrainUnit()
{
	m_nNumOfNeighbor = 0;
	m_pNeighbor = NULL;
}

TerrainUnit::TerrainUnit(const TerrainUnit& t)
{
	int i;

	m_nNumOfNeighbor = t.m_nNumOfNeighbor;
	m_pNeighbor = new unsigned int [m_nNumOfNeighbor];
	
	for(i=0;i<m_nNumOfNeighbor;i++)
		m_pNeighbor[i] = t.m_pNeighbor[i];
}

TerrainUnit::~TerrainUnit()
{
	delete [] m_pNeighbor;
}

void TerrainUnit::assignNeighbor(list<unsigned int>& n)
{
	list<unsigned int>::iterator it;
	int i;

	delete [] m_pNeighbor;
	m_nNumOfNeighbor = static_cast<unsigned short>(n.size());
	m_pNeighbor = new unsigned int[m_nNumOfNeighbor];

	for(i=0, it=n.begin();it!=n.end();it++, i++)
		m_pNeighbor[i] = *it;
}

void TerrainUnit::assignVertice(const Vector3* v)
{
	int i;



	m_Center = Vector3::ZERO;
	for(i=0;i<3;i++)
	{
		m_pVertice[i] = v[i];
		m_Center += m_pVertice[i];
	}

	m_Center /= 3;
}

Terrain::Terrain()
{
	m_nNumOfUnit = 0;
	m_pTrnUnit = NULL;
}
	
Terrain::~Terrain()
{
	delete [] m_pTrnUnit;
}

bool Terrain::loadOBJ(const wstring& fname)
{
	if(!LoadModel(fname))
	{
		//g_Log.dump("Load .OBJ Fail", __FILE__, __LINE__);
		return false;
	}

	list<unsigned int> neighborList;
	Vector3 vertice[3];
	unsigned int f, v;

	// convert .obj to .terrain
	delete [] m_pTrnUnit;
	m_nNumOfUnit = static_cast<unsigned int>(m_f.size());
	m_pTrnUnit = new TerrainUnit[m_nNumOfUnit];

	for(f=0;f<m_f.size();f++)
	{
		// assign neighbor
		neighborList = _findNeighbor(f);
		m_pTrnUnit[f].assignNeighbor(neighborList);

		for(v=0;v<3;v++)
			vertice[v] = m_v[m_f[f].m_vID[v]];
		
		m_pTrnUnit[f].assignVertice(vertice);

		if(m_f[f].mMaterialName == L"barrier")
			m_pTrnUnit[f].mType = BARRIER;
		else if( m_f[f].mMaterialName == L"boundary")
			m_pTrnUnit[f].mType = BOUNDARY;
		else
			m_pTrnUnit[f].mType = AVAILABLE;

		// assign normal
		m_pTrnUnit[f].m_Normal = Vector3::ZERO;
		for(v=0;v<3;v++)
			m_pTrnUnit[f].m_Normal += m_vn[m_f[f].m_vnID[0]];

		m_pTrnUnit[f].m_Normal.normalise();
	}
	return true;
}

bool Terrain::loadTerrain(const wstring& fname)
{
	ifstream fin;
	unsigned int f;
	int i;

	fin.open( fname.c_str(), ios::in | ios::binary );

	fin.read( reinterpret_cast< char*>(&m_nNumOfUnit), sizeof( m_nNumOfUnit));

	delete [] m_pTrnUnit;
	m_pTrnUnit = new TerrainUnit[m_nNumOfUnit];

	for(f=0;f< m_nNumOfUnit; f++)
	{
		// write vertice
		for(i=0;i<3;i++)
			fin.read( reinterpret_cast< char*>( m_pTrnUnit[f].m_pVertice[i].val), sizeof(float)*3);
		
		// write m_Center
		fin.read( reinterpret_cast< char*>( m_pTrnUnit[f].m_Center.val), sizeof(float)*3);

		// write m_Normal
		fin.read( reinterpret_cast< char*>( m_pTrnUnit[f].m_Normal.val), sizeof(float)*3);

		// write m_nNumOfNeighbor
		fin.read( reinterpret_cast< char*>( &m_pTrnUnit[f].m_nNumOfNeighbor), sizeof(m_pTrnUnit[f].m_nNumOfNeighbor));
		m_pTrnUnit[f].m_pNeighbor = new unsigned int[m_pTrnUnit[f].m_nNumOfNeighbor];
		for(i=0;i<m_pTrnUnit[f].m_nNumOfNeighbor;i++)
			fin.read( reinterpret_cast< char*>( &m_pTrnUnit[f].m_pNeighbor[i] ), sizeof( unsigned int));

		// write m_bAvailible
		fin.read( reinterpret_cast< char*>( &m_pTrnUnit[f].mType), sizeof(unsigned char));
	}

	fin.close();
	return true;

}

bool Terrain::saveTerrain(const wstring& fname)
{
	ofstream fout;
	unsigned int f;
	int i;

	fout.open( fname.c_str(), ios::out | ios::binary );

	// write num of units 4 bytes
	fout.write( reinterpret_cast< char*>(&m_nNumOfUnit), sizeof( m_nNumOfUnit));

	for(f=0;f< m_nNumOfUnit; f++)
	{
		// write vertice
		for(i=0;i<3;i++)
			fout.write( reinterpret_cast< char*>( m_pTrnUnit[f].m_pVertice[i].val), sizeof(float)*3);
		
		// write m_Center
		fout.write( reinterpret_cast< char*>( m_pTrnUnit[f].m_Center.val), sizeof(float)*3);

		// write m_Normal
		fout.write( reinterpret_cast< char*>( m_pTrnUnit[f].m_Normal.val), sizeof(float)*3);

		// write m_nNumOfNeighbor
		fout.write( reinterpret_cast< char*>( &m_pTrnUnit[f].m_nNumOfNeighbor), sizeof(m_pTrnUnit[f].m_nNumOfNeighbor));
		for(i=0;i<m_pTrnUnit[f].m_nNumOfNeighbor;i++)
			fout.write( reinterpret_cast< char*>( &m_pTrnUnit[f].m_pNeighbor[i] ), sizeof( unsigned int));

		// write m_tyep
		fout.write( reinterpret_cast< char*>( &m_pTrnUnit[f].mType), sizeof(unsigned char));
	}

	fout.close();
	return true;
}

unsigned char Terrain::_surfacePoint2DGlobal(Movement& move)
{
	unsigned int f;
	Vector3 linep, d, tri0, tri1, tri2, out;

	linep = move.currentPosition + move.probe;
	linep.y += 1000000.0f;

	//linep = Vector3(dest.x, dest.y + 1000000.0f, dest.z);
	d = Vector3::NEGATIVE_UNIT_Y;

	for(f=0;f<m_nNumOfUnit;f++)
	{
		tri0 = m_pTrnUnit[f].m_pVertice[0];
		tri1 = m_pTrnUnit[f].m_pVertice[1];
		tri2 = m_pTrnUnit[f].m_pVertice[2];

		if(LineAndTriangleIntersection(linep, d,  tri0, tri1, tri2, out))
		{
			move.newPosition = Vector3(linep.x, linep.y - out.z, linep.z);
			move.newTU = f;
			return m_pTrnUnit[f].mType;
		}
	}

	return NO_INTERSECTION;
}

void Terrain::drawEntity()
{
	unsigned int f, i;
	float tmp[16];

	glGetFloatv(GL_MODELVIEW_MATRIX, tmp);

	glPushAttrib(GL_LIGHTING_BIT | GL_POLYGON_BIT);
	if(mEnableLighting)
		glEnable(GL_LIGHTING);
	else
		glDisable(GL_LIGHTING);

	if(mRenderMode==RM_POLYGON)
		glPolygonMode( GL_FRONT_AND_BACK,GL_FILL);
	else if(mRenderMode==RM_WIRE)
		glPolygonMode( GL_FRONT_AND_BACK,GL_LINE);

	for(f=0;f<m_nNumOfUnit;f++)
	{
		if( m_pTrnUnit[f].mType==AVAILABLE)
			glColor3f(0.0f, 1.0f, 0.0f);
		else if (m_pTrnUnit[f].mType==BARRIER)
			glColor3f(1.0f, 0.0f, 0.0f);
		else
			glColor3f(0.0f, 0.0f, 1.0f);

		glBegin (GL_TRIANGLES);
		for (i =0;i<3;i++)
		{
			glNormal3fv( m_pTrnUnit[f].m_Normal.val);
			glVertex3fv (m_pTrnUnit[f].m_pVertice[i].val);
		}
		glEnd ();

		// draw normal
		if( mShowNormal)
		{
			glColor3f(0.0f, 0.0f, 1.0f);
			Vector3 tmp;
			glBegin(GL_LINES);
			glVertex3fv( m_pTrnUnit[f].m_Center.val);
			tmp = m_pTrnUnit[f].m_Center + m_pTrnUnit[f].m_Normal;
			glVertex3fv( tmp.val);
			glEnd();
		}
	}

	glPopAttrib();
}

unsigned char Terrain::surfacePoint2D(Movement& move, bool bGlobalSearch)
{
	Vector3 linep, d, tri0, tri1, tri2, out, dest;
	unsigned int _src, count;
	dest = move.currentPosition + move.probe;

	// find which unit is correct one
	if(bGlobalSearch)// global search
	{
		return _surfacePoint2DGlobal(move);
	}
	else
	{
		linep = Vector3(dest.x, dest.y + 100000.0f , dest.z);
		d = Vector3::NEGATIVE_UNIT_Y;
		tri0 = m_pTrnUnit[move.currentTU].m_pVertice[0];
		tri1 = m_pTrnUnit[move.currentTU].m_pVertice[1];
		tri2 = m_pTrnUnit[move.currentTU].m_pVertice[2];
		_src = move.currentTU;
		count = 0;

		while( !LineAndTriangleIntersection(linep,d, tri0, tri1, tri2, out) && count < MAX_SEARCH_UNIT)
		{
			_src = _nearestNeighbor2Target(_src, dest);
			tri0 = m_pTrnUnit[_src].m_pVertice[0];
			tri1 = m_pTrnUnit[_src].m_pVertice[1];
			tri2 = m_pTrnUnit[_src].m_pVertice[2];
			count ++;
		}

		if(count >= MAX_SEARCH_UNIT)
		{
			return _surfacePoint2DGlobal(move);
		}
		else
		{
			move.currentPosition = Vector3(linep.x, linep.y - out.z, linep.z);
			move.newTU = _src;
			return m_pTrnUnit[_src].mType;
			//new_pos = Vector3(out.x, out.y + offset , out.z);
			//return _src;
		}
	}

}

unsigned int Terrain::_nearestNeighbor2Target(unsigned int src, const Vector3& target)
{
	unsigned short f;
	unsigned int ret;
	float min_dist, dist;
	
	min_dist = (numeric_limits<float>::max)();
	ret = -1;

	for(f=0;f<m_pTrnUnit[src].m_nNumOfNeighbor;f++)
	{
		dist = m_pTrnUnit[m_pTrnUnit[src].m_pNeighbor[f]].m_Center.distance(target);
		if( dist < min_dist)
		{
			min_dist = dist;
			ret = m_pTrnUnit[src].m_pNeighbor[f];
		}
	}

	return ret;
}

list<unsigned int> Terrain::_findNeighbor(unsigned int t) const
{
	list<unsigned int> ret;
	deque<unsigned int>::iterator it1, it2;

	unsigned int f, i, j;
	m_f[t].m_vID.begin();

	for(f=0;f<m_f.size();f++)// for each face
	{// if they share vertice, push to the list
		for(i=0;i<m_f[t].m_vID.size();i++)
		{
			for(j=0;j<m_f[f].m_vID.size();j++)
			{
				if(m_f[t].m_vID[i] == m_f[f].m_vID[j])
					ret.push_back(f);
			}
		}
		//for(it1=m_f[t].m_vID.begin(); it1!=m_f[t].m_vID.end();it1++)
		//{
		//	for(it2=m_f[f].m_vID.begin(); it2!= m_f[f].m_vID.end();it2++)
		//	{
		//		if( *it1== *it2 )
		//			ret.push_back(f);
		//	}
		//}
	}

	ret.remove( t );
	ret.unique();

	return ret;
}

FeetOnTerrain::FeetOnTerrain()
{
	m_pTerrain = NULL;
	m_fProbe = 0.0f;
	m_bOnTerrain = false;
}

FeetOnTerrain::FeetOnTerrain(const FeetOnTerrain& ft)
{
	m_pTerrain					= ft.m_pTerrain;
	m_nCurrentTUIndex	= ft.m_nCurrentTUIndex;
	m_fProbe					= ft.m_fProbe;
	m_bOnTerrain			= ft.m_bOnTerrain;
}

FeetOnTerrain::~FeetOnTerrain()
{

}

FeetOnTerrain::MOVE_RESULT FeetOnTerrain::putOnTerrain(Terrain* trn, const Vector3& pos)
{
	unsigned char result;
	Vector3 new_pos;
	m_pTerrain = trn;
	Movement move;

	move.currentPosition = pos;
	move.probe = Vector3::ZERO;

	result = m_pTerrain->surfacePoint2D(move, true);

	if ( result == AVAILABLE)
	{
		setPosition(move.newPosition);
		m_bOnTerrain = true;
		return DONE;
	}
	else// if( result == BARRIER || result == BOUNDARY)
	{
		m_bOnTerrain = false;
		return BLOCK;
	}
}

void FeetOnTerrain::pickFromTerrain()
{
	m_pTerrain = NULL;
	m_bOnTerrain = false;
}

FeetOnTerrain::MOVE_RESULT FeetOnTerrain::move(const Vector3& dir, float offset)
{
	if(!m_bOnTerrain)
		return NOT_ON_TERRAIN;

	Movement move;
	unsigned char result;

	move.currentPosition = getPosition();
	move.probe = dir;
	move.currentTU = m_nCurrentTUIndex;

	result = m_pTerrain->surfacePoint2D(move);

	if(result==AVAILABLE)
	{
		setPosition(move.newPosition);
		return DONE;
	}
	else if( result==BARRIER)
	{
		return BLOCK;
	}
	else
	{
		return BOUNDARY;
	}
}

FeetOnTerrain::MOVE_RESULT FeetOnTerrain::moveForward(float d)
{
	
	return DONE;
}

void FeetOnTerrain::setProbe(float d)
{
	m_fProbe = d;
}

}